/*
 * IllGames / Illusion Scene files
 *
 * These are plain simple PNG files, with additional data attached at the end.
 */
#include "main.h"

/******** supported character card types, feel free to expand the list! ********/
struct { char *magic; char *fnpre; char type; char *reg; } charatypes[] = {
   /* magic bytes                 file prefix   type   registry key to get UserData dir */
   { "【HCChara】",                "HC",         0,     "illgames\\honeycome" },
   { "【KoiKatuChara】",           "KK",         0,     "illusion\\koikatu\\koikatu" },
   { "【KoiKatuCharaSP】",         "KK",         0,     "illusion\\koikatu\\koikatu" },
   { "【KoiKatuCharaS】",          "KKS",        0,     "illusion\\koikatu\\koikatu_sunshine" },
   { "【HoneySelectCharaMale】",   "HS",         'M',   "illusion\\honeyselect" },
   { "【HoneySelectCharaFemale】", "HS",         'F',   "illusion\\honeyselect" },
   { "【AIS_Chara】",              "HS2",        0,     "illusion\\honeyselect2" },
   { "【RG_Chara】",               "RG",         1,     "illusion\\roomgirl" }
};

#define CHARAW 252
#define CHARAH 352
#ifdef __WIN32__
#include <windows.h>
#endif

/**
 * Parse a characard
 *  ptr  in / out, points to the matched card magic, on return should point to the end of card data
 *  t    in, card type, selects a row from the table above
 *  chr  out, start of to-be-saved card data
 *  face out, start of face image png (or NULL)
 *  size out, length of face image (or 0)
 *  crc  out, checksum (to detect duplicates)
 *  c    out, either 'F' (female) or 'M' (male)
 *  name out, generated card file name
*/
static uint8_t *parsecard(uint8_t *ptr, int t, uint8_t **chr, uint8_t **face, uint32_t *size, uint32_t *crc, int *c, char *name)
{
   uint8_t *p, *e;
   uint32_t num, s;

   /* ptr points to the card magic */
   *chr = ptr - (charatypes[t].type >= 'A' ? 1 : 5);
   ptr += strlen(charatypes[t].magic);
   if(charatypes[t].type >= 'A') {
      /* --- black sheep HoneySelect --- */
      *c = charatypes[t].type;
      /* version */
      ptr += 4;
      /* number of property lists */
      num = (ptr[0] | (ptr[1] << 8));
      ptr += 4;
      /* get max(proplist.pos + proplist.size) */
      for(e = p = ptr + num * 0x94; num; num--) {
         ptr += 128 + 4;
         s = (ptr[0] | (ptr[1] << 8) | (ptr[2] << 16)) + (ptr[8] | (ptr[9] << 8) | (ptr[10] << 16));
         ptr += 16;
         if(p + s > e) e = p + s;
      }
      ptr = e;
      *face = NULL;
      *size = 0;
   } else {
      /* --- every other cards --- */
      /* version */
      ptr += *ptr + 1;
      /* face image */
      *size = ptr[0] | (ptr[1] << 8) | (ptr[2] << 16);
      ptr += 4;
      if(*size) {
         if(charatypes[t].type != 1)
            *face = ptr;
         ptr += *size;
      } else
         *face = NULL;
      /* AIS_Chara has two UUIDs here */
      if(*ptr == '$' && ((ptr[3] >= '0' && ptr[3] <= '9') || (ptr[3] >= 'a' && ptr[3] <= 'f'))) {
         ptr += *ptr + 1;
         ptr += *ptr + 1;
      }
      /* RG_Chara has the face image here */
      if(charatypes[t].type == 1) {
         *size = ptr[0] | (ptr[1] << 8) | (ptr[2] << 16);
         ptr += 4;
         if(*size) {
            *face = ptr;
            ptr += *size;
         } else
            *face = NULL;
      }
      /* header */
      ptr += (ptr[0] | (ptr[1] << 8) | (ptr[2] << 16)) + 4;
      /* properties */
      p = ptr;
      ptr += (ptr[0] | (ptr[1] << 8) | (ptr[2] << 16) | (ptr[3] << 24)) + 8;
      while(p < ptr && memcmp(p, "sex", 3)) p++;
      *c = p + 3 < ptr && p[3] == 1 ? 'F' : 'M';
   }
   /* generate file name */
   *crc = crc32(*chr, (uintptr_t)ptr - (uintptr_t)*chr);
   sprintf(name, "%s_%c_%04X_%03u", charatypes[t].fnpre, *c, *crc, numsheets + 1);
   return ptr;
}

/**
 * Generate preview image from face image
 */
static void genpreview(uint32_t *preview, uint8_t *face, uint32_t fsiz, int c)
{
    uint32_t bg, *img;
    int i, j, x, y, w, h, s, a, b;

    img = sprites_decode(face, fsiz, &x, &y);

    /* if we have a face picture use it to create preview */
    if(img && x && y) {
        /* fill with solid color from the face image */
        for(i = 0; i < CHARAW * CHARAH; i++)
            preview[i] = img[0];
        a = b = 0; s = x;
        bg = (img[0] & 0xf8f8f8);
        for(i = 0; i < x / 3; i++) {
            /* crop left */
            for(j = 0; j < y && (img[j * x + i] & 0xf8f8f8) == bg; j++);
            if(j == y) { a++; s--; }
            /* crop right */
            for(j = 0; j < y && (img[j * x + (x - i - 1)] & 0xf8f8f8) == bg; j++);
            if(j == y) s--;
        }
        for(i = 0; i < y / 3; i++) {
            /* crop top */
            for(j = 0; j < x && (img[i * x + j] & 0xf8f8f8) == bg; j++);
            if(j == x) b++;
        }
        y -= b;
        w = CHARAW; h = CHARAW * y / s;
        if(h > CHARAH) { h = CHARAH; w = CHARAH * s / y; }
        resize(preview, (CHARAW - w) / 2, CHARAH - h, w, h, CHARAW, (uint32_t*)(img + (b * x + a)), s, y, x);
        free(img);
    } else
        /* fill with solid color depending on sex */
        for(i = 0; i < CHARAW * CHARAH; i++)
            preview[i] = c == 'F' ? 0xfff6baf3 : 0xfffec2a8;
}

/**
 * Detect if this is a scene card
 */
int illsc_detect(char *path, uint8_t *buf, uint64_t size)
{
    uint8_t *png, *ptr;
    int i, ret = 0;

    if(!path || !*path || !buf || !size || memcmp(buf, "\x89PNG", 4)) return 0;
    if((png = getfile(path, &size))) {
        for(ptr = png + 8; ptr < png + size - 8 && memcmp(ptr + 4, "IEND", 4); ptr += ((ptr[1] << 16) | (ptr[2] << 8) | ptr[3]) + 12);
        if(ptr + 42 < png + size) {
            for(; !ret && ptr < png + size; ptr++)
                for(i = 0; i < (int)(sizeof(charatypes)/sizeof(charatypes[0])); i++)
                    if(!memcmp(ptr, charatypes[i].magic, strlen(charatypes[i].magic))) {
                        ret = 1;
                        break;
                    }
        }
        free(png);
    }
    return ret;
}

/**
 * Load all characards from a scene card
 */
void illsc_load(char *path, int isdir)
{
    uint8_t *buf, *ptr, *start, *chr, *face = NULL;
    uint32_t fsize, dsize, crc, list[256];
    uint64_t offs, size, total;
    char *fn, name[64];
    int i, k, c, sheet, ncards = 0;
#ifdef __WIN32__
    HKEY hKey;
    WCHAR szName[MAX_PATH], szPath[MAX_PATH];
    DWORD dwType, cbData = sizeof(szPath);
#endif

    if(!path || !*path || isdir) return;

    status(LOADING, 0, 100);

    if((buf = getfile(path, &size))) {
        if(memcmp(buf, "\x89PNG", 4)) { free(buf); return; }
        for(ptr = buf + 8; ptr < buf + size - 8 && memcmp(ptr + 4, "IEND", 4); ptr += ((ptr[1] << 16) | (ptr[2] << 8) | ptr[3]) + 12);
        if(ptr + 42 < buf + size) {
            sprites_free();
            for(start = ptr, total = (uintptr_t)buf + size - (uintptr_t)ptr; ptr < buf + size && numsheets < 256; ptr++)
                for(k = 0; k < (int)(sizeof(charatypes)/sizeof(charatypes[0])); k++)
                    if(!memcmp(ptr, charatypes[k].magic, strlen(charatypes[k].magic))) {
                        ncards++;
                        status(LOADING, (uintptr_t)ptr - (uintptr_t)start, total);
                        offs = (uintptr_t)ptr - (uintptr_t)buf;
                        ptr = parsecard(ptr, k, &chr, &face, &fsize, &crc, &c, name);
                        dsize = (uintptr_t)ptr - (uintptr_t)chr;
                        debug("offset %lx size %lu card '%s'", offs, dsize, charatypes[k].magic);
                        for(i = 0; i < numsheets && list[i] != crc; i++);
                        if(i == numsheets) {
                            list[i] = crc;
                            if((sheet = sprites_newsheet(-1, name, CHARAW, CHARAH)) >= 0) {
                                genpreview(sheets[sheet].data, face, fsize, c);
                                if((sheets[sheet].extra = (uint8_t*)malloc(dsize))) {
                                    memcpy(sheets[sheet].extra, chr, dsize);
                                    sheets[sheet].esiz = dsize;
                                    sprites_add(sheet, name, CHARAW, CHARAH, 0, 0, sheet, 0, 0, CHARAW, CHARAH, 0, -1);
#ifdef __WIN32__
                                    if(charatypes[k].reg && charatypes[k].reg[0]) {
                                        memcpy(szName, L"software\\", 18);
                                        MultiByteToWideChar(CP_UTF8, 0, charatypes[k].reg, -1, szName + 9, PATH_MAX - 10);
                                        if(!RegOpenKeyExW(HKEY_CURRENT_USER, szName, 0, KEY_READ, &hKey) &&
                                            !RegGetValueW(hKey, NULL, L"INSTALLDIR", RRF_RT_REG_SZ, &dwType, szPath, &cbData)) {
                                                RegCloseKey(hKey);
                                                for(i = 0; szPath[i]; i++);
                                                if(i && szPath[i - 1] == L'\\') i--;
                                                if(c == 'F')
                                                    memcpy(szPath + i, L"\\UserData\\chara\\female", 44);
                                                else
                                                    memcpy(szPath + i, L"\\UserData\\chara\\male", 40);
                                        } else szPath[0] = 0;
                                        dsize = WideCharToMultiByte(CP_UTF8, 0, szPath, -1, NULL, 0, NULL, NULL);
                                        if((sheets[sheet].path = (char*)(malloc(dsize + 1)))) {
                                            WideCharToMultiByte(CP_UTF8, 0, szPath, -1, sheets[sheet].path, dsize + 1, NULL, NULL);
                                            sheets[sheet].path[dsize] = 0;
                                        }
                                    }
#endif
                                } else {
                                    engine_features |= EF_NOMEM;
                                    debug("memory allocation error on getting characard data (%u bytes)", dsize);
                                    free(sheets[i].data);
                                    if(numsprites) {
                                        numsprites--;
                                        if(sprites[numsprites].frame) free(sprites[numsprites].frame);
                                    }
                                    numsheets--;
                                }
                            }
                        }
                        ptr--;
                        break;
                    }
        }
        free(buf);
        fn = strrchr(path, '/');
        if(!fn) fn = strrchr(path, '\\');
        if(fn) fn++; else fn = path;
        strncpy(engine_game, fn, sizeof(engine_game) - 1);
    }
    debug("Total %u characards found, %u was unique", ncards, numsheets);

    engine_features |= EF_DECRYPT;
}

/**
 * Free any additional resources that illsc_load() allocated
 */
void illsc_unload(void)
{
}

/**
 * Decrypt and unpack all characards in a scene card
 */
void illsc_decrypt(char *path, int isdir)
{
    char tmp[PATH_MAX + FILENAME_MAX + 1];
    uint8_t *buf;
    uint64_t size;
    int i, n, l;

    (void)path; (void)isdir;
    if(!numsheets || !numsprites) return;
    fn[0] = 0;

    /* should be numsprites == numsheets */
    n = numsprites < numsheets ? numsprites : numsheets;

    /* if we have at least one characard without a path, ask the user where to save */
    for(i = 0; i < n && sheets[i].path && sheets[i].path[0]; i++);
    if(i < n) {
        opendialog(SAVEDIR, 1);
        if(!fn[0]) return;
    }

    status(SAVING, 0, 100);
    for(i = 0; i < n; i++) {
        if((buf = sprites_encode(i, &size))) {
            strcpy(tmp, sheets[i].path && sheets[i].path[0] ? sheets[i].path : fn);
            l = strlen(tmp);
            if(l && tmp[l - 1] != '/' && tmp[l - 1] != '\\') tmp[l++] = '/';
            strcpy(tmp + l, sprites[i].name);
            strcat(tmp + l, ".png");
            putfile(tmp, buf, size, NULL, 0, NULL, 0);
            free(buf);
        } else
            debug("unable to encode png for '%s'", sprites[i].name);
        status(SAVING, i, n);
    }
}

/**
 * Draw map tiles to off-screen pixel buffer
 */
void illsc_drawmap(map_t *map)
{
    (void)map;
}

/**
 * Save translated text
 */
void illsc_translate(char *path)
{
    msgbox(0, UNIMPLEMENTED, path);
}

ENGINE_MODULE(illsc, "IllGames / Illusion Scenes")
